#include "dicom/handler/file_download_handler.h"
#include "dicom/storage/decrypt.h"
#include <gwbase/base/util.h>
#include <gwbase/base/logging.h>
#include <gwbase/base/thread.h>
#include <json/json.h>
#include "dicom/web_server.h"
#include "dicom/handler/handler_common.h"
#include <surbasic/surbasic.h>

namespace dicom {

class OnionResponseWriter : public WriterStream {
 public:
  OnionResponseWriter(onion_response* resp) : resp_(resp) {}
  virtual ~OnionResponseWriter() {}

  virtual int Write(const void* buf, size_t len) {
    const char* pos = reinterpret_cast<const char*>(buf);
    size_t remain = len;
    while (remain > 0) {
      ssize_t n = ::onion_response_write(resp_, pos, remain);
      if (n < 0) return -1;
      pos += len;
      remain -= n;
    }

    // push to network
    if (::onion_response_flush(resp_) < 0) {
      return -1;
    }

    return static_cast<int>(len);
  }

  virtual int Finalize() { return ::onion_response_flush(resp_); }
  virtual void Close() {}

 private:
  onion_response* resp_;
};

static HttpHandler* CreateFileUploadHandler(onion_request* conn) {
  return new FileDownloadHandler(conn);
}

void FileDownloadHandler::RegisterHandler(WebServer* s) {
  s->AddHandler("/api/file/download", "GET",
                boost::bind(CreateFileUploadHandler, _1));

  s->AddHandler("/api/file/([A-Z0-9]*)/content", "GET",
                boost::bind(CreateFileUploadHandler, _1));
}

int FileDownloadHandler::HandleRequest(onion_request* conn,
                                       onion_response* resp) {
  Status s;
  CredentialPtr c;

  s = CheckToken(conn, &c);
  if (!s.ok()) {
    LOG_ERROR << "oauth error: " << s.ToString();
    SendSimpleResponse(resp, 401, "oauth error");
    return OCS_PROCESSED;
  }

  std::string id;
  if (params_.empty()) {
    const std::string id_s = GetQueryString(conn, "id");
    if (!id_s.empty()) id = id_s;
  } else {
    id = params_[0];
  }

  if (id.empty()) {
    LOG_ERROR << "file id is empty";
    SendSimpleResponse(resp, 400, "file id is empty");
    return OCS_PROCESSED;
  }

  LOG_DEBUG << "download file id: " << id;
  DmvDBPtr dmv = GetGateway()->dmv_mgr()->GetDB(c->email);
  FileObject fo;
  if (dmv->GetFileByID(id, &fo) == false) {
    LOG_ERROR << "file not found: " << id;
    SendSimpleResponse(resp, 404, "file not found");
    return OCS_PROCESSED;
  }

  LogHippaOperation(OP_DOWNLOAD_FILE, dmv, c, fo.parentId, fo.name);

  LRUFileCache* cache = GetGateway()->cache();
  LRUFileCache::Handle* h = cache->Lookup(id);
  FileInfo fi;
  if (h == NULL) {
    std::string cloud_id = dmv->GetFileCloudId(id);
    if (cloud_id.empty()) {
      LOG_ERROR << "failed to download file from surcloud: " << id;
      SendSimpleResponse(resp, 500, "failed to download file from surcloud");
      return OCS_PROCESSED;
    }

    std::string tmp_file;

    // = "/tmp/" + GenFileId();
    if (cache->AllocTmpFile("Download", fo.size, &tmp_file) == false) {
      LOG_ERROR << "failed to download file from surcloud: " << id;
      SendSimpleResponse(resp, 500, "failed to download file from surcloud");
      return OCS_PROCESSED;
    }

    std::string aes_key;
    std::string src_md5;
    bool download_result = SDBasic::DownloadWithoutDecryption(
        c->surdoc_key, cloud_id, tmp_file, &src_md5, &aes_key);

    if (download_result == false) {
      dicom::DeleteFile(tmp_file);
      cache->ReleaseTmpFile("Download", tmp_file);
      LOG_ERROR << "failed to download file from surcloud: " << id;
      SendSimpleResponse(resp, 500, "failed to download file from surcloud");
      return OCS_PROCESSED;
    }

    s = StatPath(tmp_file, &fi);
    if (!s.ok() || !fi.is_regular()) {
      cache->ReleaseTmpFile("Download", tmp_file);
      LOG_ERROR << "cann't open input stream";
      SendSimpleResponse(resp, 500, "cann't open input stream");
      return OCS_PROCESSED;
    }

    if (fo.digest.empty()) {
      fo.digest = src_md5;
    }

    // not sticky
    h = cache->InsertFromTmpFile(fo.local_id, tmp_file, fo.size, fo.digest, "",
                                 Sticky::removable);
    if (h == NULL) {
      dicom::DeleteFile(tmp_file);
      LOG_ERROR << "can't open input stream";
      SendSimpleResponse(resp, 500, "cann't open input stream");
      return OCS_PROCESSED;
    }

    if (!fo.digest.empty()) {
      HashEntry hash;
      hash.src_md5 = fo.digest;
      hash.hexkey = aes_key;
      hash.status = HASH_DOWNLOADED;
      GetGateway()->hcache()->UpdateHashEntry(&hash);
    }

    fo.hex_key = aes_key;
    dmv->UpdateFile(fo);
  }

  LOG_DEBUG << "DOWNLOAD: " << id << ": " << fo.size;

  // ok, we have downloaded file
  assert(h != NULL);

  s = StatPath(cache->FilePath(h), &fi);
  if (!s.ok() || !fi.is_regular()) {
    cache->Release(h);
    LOG_ERROR << "failed to open stream: " << id;
    SendSimpleResponse(resp, 500, "cann't open input stream");
    return OCS_PROCESSED;
  }

  FileReader reader;
  s = reader.Open(cache->FilePath(h));
  if (!s.ok()) {
    cache->Release(h);
    LOG_ERROR << "failed to open stream: " << id;
    SendSimpleResponse(resp, 500, "cann't open input stream");
    return OCS_PROCESSED;
  }

  ::onion_response_set_code(resp, 200);
  ::onion_response_set_header(resp, "Content-Type", "application/octet-stream");
  ::onion_response_set_header(resp, "Digest", fo.digest.c_str());
  ::onion_response_set_length(resp, fo.size);

  OnionResponseWriter writer(resp);
  Decryptor dec;
  s = dec.DoDecrypt(fo.hex_key, &reader, &writer);
  reader.Close();
  writer.Close();

  cache->Release(h);
  return OCS_PROCESSED;
}
}
